【译】RxJS Pipeable Operators - Pipe

Pipeable Operators

从版本5.5开始,我们发布了”pipe operators”,可以直接在rxjs/operators访问操作符。 与rxjs/ add/operator/*中的操作符相比,这仅仅是一种更好的方法来引入所需的操作符。

注意:通过rxjs/operators引入的操作符,如果你的构建程序不更改配置的话,最后打包的文件会变大,不过没关系,Known Issues部分会给出解决办法。

改名的操作符

由于操作符可独立于Observable对象使用,操作符不能与JavaScript关键字冲突。 因此对某些操作符的可移植版本的名称进行更改。 有以下操作符:

  1. do -> tap
  2. catch -> catchError
  3. switch -> switchAll
  4. finally -> finalize

let操作符现在是Observable的一部分,并且不能被导入。
source$.let(myOperator) -> source$.pipe(myOperator)

现在移除了toPromise()操作符,因为它返回的是Observable而并不是Promise。现在使用Observable.toPromise()实例方法。

因为throw是关键字,用_throw来导入。import { _throw } from 'rxjs/observable/throw'

如果_这个符号很困扰你,可以使用以下方法:

1
2
3
4
import { ErrorObservable } from 'rxjs/observable/ErrorObservable';
...
const e = ErrorObservable.create(new Error('My bad'));
const e2 = new ErrorObservable(new Error('My bad too'));

Why?

patched操作符链式调用的问题:

  1. 任何导入patched操作符的库都会为该库的所有使用者增加Observable.prototype,从而创建盲目的依赖关系。 如果删除这类库,这可能会不知不觉地影响其他人的引用。 使用pipeables,你必须将你需要的操作符导入到正在使用它们的每个文件中。

  2. 直接添加到原型上的操作符不会像rollupwebpack这样的工具“进行摇树优化”。 pipeable操作符将会像它们直接从模块中引入的功能一样。

  3. 无法通过任何构建工具或lint rule可靠地检测到正在应用中导入但并未使用的操作符。 这意味着你可能会导入scan,但不再使用它,它仍然被添加到你的输出包中。 对于pipeable操作符,如果你不使用它,lint rule可以为你检测出来。

  4. 这种设计很棒。 构建自己的自定义操作符变得非常容易,现在它们的工作方式与rxjs中的所有其他操作符一样。 你不需要扩展Observable或重写lift

What?

什么是pipeable操作符? 简而言之,可以与当前let运算符一起使用的函数。 它曾经是名称的起源(“lettable”),但是这让人觉得很奇怪,所以改为“pipeable”,因为它们打算与pipe功能一起使用。 一个pipeable操作符基本上是任何返回一个带有签名函数的函数:<T,R>(source:Observable <T>)=> Observable <R>

现在在Observable.prototype.pipe中有一个’Observable内置的pipe`方法,它类似于以前的链式组合操作符。(如下所示)

rxjs / util / pipe上还有pipe实用功能,可用于从其他pipeable操作符构建可重复使用的pipeable操作符。

Usage

你可以在rxjs / operators复数)下导入所需的操作符。 还推荐直接使用Observable创建方法,如下所示,使用`range’:

1
2
3
4
5
6
7
8
9
10
11
import { range } from 'rxjs/observable/range';
import { map, filter, scan } from 'rxjs/operators';

const source$ = range(0, 10);

source$.pipe(
filter(x => x % 2 === 0),
map(x => x + x),
scan((acc, x) => acc + x, 0)
)
.subscribe(x => console.log(x))

Build Your Own Operators Easily

实际上,你可以用let来做到这一点……但是构建你自己的操作符就像现在写一个函数一样简单。 请注意,可以无缝地与其他rxjs操作符组成自定义操作符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import { interval } from 'rxjs/observable/interval';
import { filter, map, take, toArray } from 'rxjs/operators';

/**
* an operator that takes every Nth value
*/
const takeEveryNth = (n: number) => <T>(source: Observable<T>) =>
new Observable<T>(observer => {
let count = 0;
return source.subscribe({
next(x) {
if (count++ % n === 0) observer.next(x);
},
error(err) { observer.error(err); },
complete() { observer.complete(); }
})
});

/**
* You can also use an existing operator like so
*/
const takeEveryNthSimple = (n: number) => <T>(source: Observable<T>) =>
source.pipe(filter((value, index) => index % n === 0 ))

/**
* And since pipeable operators return functions, you can further simplify like so
*/
const takeEveryNthSimplest = (n: number) => filter((value, index) => index % n === 0);

interval(1000).pipe(
takeEveryNth(2),
map(x => x + x),
takeEveryNthSimple(3),
map(x => x * x),
takeEveryNthSimplest(4),
take(3),
toArray()
)
.subscribe(x => console.log(x));
// [0, 2304, 9216]

Known Issues

TypeScript < 2.4

在TypeScript 2.3及更低版本中,需要将类型添加到传递给操作符的函数中,因为类型不能在TypeScript 2.4之前推断出来。 在TypeScript 2.4中,类型将通过合成来正确推断。

TS 2.3及以下

1
2
3
4
range(0, 10).pipe(
map((n: number) => n + '!'),
map((s: string) => 'Hello, ' + s),
).subscribe(x => console.log(x))

TS 2.4及以上

1
2
3
4
range(0, 10).pipe(
map(n => n + '!'),
map(s => 'Hello, ' + s),
).subscribe(x => console.log(x))

Build and Treeshaking

main(或重新导出)文件导入时,应用程序包有时可能会增长。 现在从rxjs / operators中导入可管理的操作符,但是如果不更改构建过程,通常会导致更大的应用程序包。 这是因为默认rxjs / operators会解析为rxjs的CommonJS输出。

为了使用新的pipeable操作符而不增加打包的体积,需要更改Webpack配置。 这种方法只适用于Webpack 3+,因为它依赖于来自Webpack 3新的特性ModuleConcatenationPlugin

path-mapping

与rxjs 5.5一起发布的版本是使用ES5和ES2015语言级别的ECMAScript模块格式(导入和导出)的rxjs版本。 你可以在node_modules/rxjs/_esm5node_modules/rxjs/_esm2015(“esm”代表ECMAScript模块,数字“5”或“2015”代表ES语言级别)中找到这些分布。 在你的代码中,应该从rxjs / operators中导入,但在Webpack配置文件中,需要将导入重新映射到ESM5(或ESM2015)版本。

如果你需要使用(’rxjs / _esm5 / path-mapping’)`,将收到一个函数,该函数返回一个键值对的对象,将每个输入映射到磁盘上的文件位置。 如下:

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const rxPaths = require('rxjs/_esm5/path-mapping');
const webpack = require('webpack');
const path = require('path');

module.exports = {
entry: 'index.js',
output: 'bundle.js',
resolve: {
// Use the "alias" key to resolve to an ESM distribution
alias: rxPaths()
},
plugins: [
new webpack.optimize.ModuleConcatenationPlugin()
]
};

or

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const DashboardPlugin = require('webpack-dashboard/plugin');
const nodeEnv = process.env.NODE_ENV || 'development';
const isProd = nodeEnv === 'production';
const rxPaths = require('rxjs/_esm5/path-mapping');

var config = {
devtool: isProd ? 'hidden-source-map' : 'cheap-eval-source-map',
context: path.resolve('./src'),
entry: {
app: './index.ts',
vendor: './vendor.ts'
},
output: {
path: path.resolve('./dist'),
filename: '[name].bundle.js',
sourceMapFilename: '[name].map',
devtoolModuleFilenameTemplate: function (info) {
return "file:///" + info.absoluteResourcePath;
}
},
module: {
rules: [
{ enforce: 'pre', test: /\.ts$|\.tsx$/, exclude: ["node_modules"], loader: 'ts-loader' },
{ test: /\.html$/, loader: "html" },
{ test: /\.css$/, loaders: ['style', 'css'] }
]
},
resolve: {
extensions: [".ts", ".js"],
modules: [path.resolve('./src'), 'node_modules'],
alias: rxPaths()
},
plugins: [
new webpack.DefinePlugin({
'process.env': { // eslint-disable-line quote-props
NODE_ENV: JSON.stringify(nodeEnv)
}
}),
new webpack.HashedModuleIdsPlugin(),
new webpack.optimize.ModuleConcatenationPlugin(),
new HtmlWebpackPlugin({
title: 'Typescript Webpack Starter',
template: '!!ejs-loader!src/index.html'
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: Infinity,
filename: 'vendor.bundle.js'
}),
new webpack.optimize.UglifyJsPlugin({
mangle: false,
compress: { warnings: false, pure_getters: true, passes: 3, screw_ie8: true, sequences: false },
output: { comments: false, beautify: true },
sourceMap: false
}),
new DashboardPlugin(),
new webpack.LoaderOptionsPlugin({
options: {
tslint: {
emitErrors: true,
failOnHint: true
}
}
})
]
};

module.exports = config;

无法控制构建过程时

如果无法控制构建过程(或者无法升级到Webpack 3+),则上述解决方案将无法工作。 因此,从rxjs / operators导入可能会使你的应用程序包变大。 但是,仍然有一种方法可以使用pipeable操作符。 你必须使用深导入,这与你在5.5版之前导入的方式和pipeable操作符相似:

1
2
3
4
5
6
7
import { map, filter, reduce } from 'rxjs/operators';

// 变为:

import { map } from 'rxjs/operators/map';
import { filter } from 'rxjs/operators/filter';
import { reduce } from 'rxjs/operators/reduce';
坚持原创技术分享,您的支持将鼓励我继续创作!

热评文章